using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using CodeSnippet.Config;
using CodeSnippet.Forms;
using CodeSnippet.Helpers;
using CodeSnippet.Properties;
using WindowsLive.Writer.Api;
using WLWPluginBase.Win32;

namespace CodeSnippet
{
    /// <summary>
    /// The plugin entry point.
    /// </summary>
#pragma warning disable 618,612
    [WriterPlugin("7290180F-2379-47c4-9962-3ECC30C9EA69", "Code Snippet", "Resources.CodeSnippet.png",
#pragma warning restore 618,612
        PublisherUrl = "http://lvildosola.blogspot.com",
        Description = "Insert syntax-higlighted snippets of code.")]
    [InsertableContentSource("Code Snippet")]
    [CLSCompliant(false)]
    public class CodeSnippetPlugin : ContentSource
    {
        #region Constants
        private const RegexOptions RXO_DEFAULTS = (RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
        private const string RXP_EXTRACT_CODE_SNIPPET = "<(div|pre)[\\s]+.*id=[\"]?(codeSnippetWrapper|codeSnippet)[\"]?.*?(?<codeSnippet>.*?)</(div|pre)>";
        private const string RXP_EXTRACT_LNUM = "<span[\\s]+\\bid=[\"]?lnum[\\d]*[\"]?\\b[^>]*>.*?</span>[\\s]?";
        private const string RXP_EXTRACT_CRLF = @"(<BR>|<!--CRLF-->)";
        private const string RXP_EXTRACT_HTML = @"<(.|\n)*?>";
        private const string RXP_REPLACE_LNUM = @"^(?:\r\n|\r|\n)*(?<lineNumber>\s*\d+:\s{1})(?<line>.*)(?!\r\n|\r|\n)*$";
        private const string RXG_CODE_SNIPPET = "codeSnippet";
        #endregion Constants

        #region Overrides
        /// <summary>
        /// Process the code snippet based on the respective settings.
        /// </summary>
        /// <param name="dialogOwner">Windows handle to whom the pluggin belongs.</param>
        /// <param name="content">Reference placeholder for the returned content</param>
        /// <returns>Returns the processed content.</returns>
        public override DialogResult CreateContent(IWin32Window dialogOwner, ref string content)
        {
            // Assume we were unable to process anything.
            var result = DialogResult.Cancel;

            // Load the configuration.
            var config = CodeSnippetConfig.Load();

            // Determine if we are running in silent mode.
            if (config.General.RunSilent)
            {
                // Determine if the user wants to restore viewing the form.
                if (Control.ModifierKeys == Keys.Control)
                {
                    // Claer the silent mode flag, it will be checked later.
                    config.General.RunSilent = false;
                }
                else
                {
                    // Process the contents of the clipboard.
                    content = FormatHelper.Format(config.General.ActiveLanguage, config.Editor, config.Style,
                        Clipboard.GetText(), config.Editor.TrimIndentOnPaste);
                    result = DialogResult.OK;
                }
            }

            // Determine if we are not running in silet mode.
            if (!config.General.RunSilent)
            {
                // Set a flag to indicate we don't want to re-ask the user about the selected text everytime
                // a switch of the view mode is made.
                var queryUserAboutSelectedText = true;
                // Assume we are okay to process the selected HTML, if any.
                var processSelectedHtml = false;

                // Loop to allow the user to switch between Compact and Full modes.
                do
                {
                    // Prepare a placeholder for the form.
                    ICodeSnippetForm frm;
                    // Determine if we are running in compact mode.
                    if (CodeSnippetViewMode.Compact == config.General.ActiveMode)
                    {
                        // Compact mode requires the compact Code Snippet form.
                        frm = new CodeSnippetCompactForm(config);
                    }
                    else
                    {
                        // Full mode requires the normal Code Snippet form.
                        frm = new CodeSnippetForm(config);

                        // Do we have a potential block of text that needs to be edited as a code snippet?
                        var item = ((dialogOwner == null) ? null : Win32EnumWindows.FindByClassName(dialogOwner.Handle, "Internet Explorer_Server"));
                        if ((null != item) && !string.IsNullOrEmpty(Win32IEHelper.GetSelectedText(item.Handle)))
                        {
                            // Get the selected HTML to process.
                            var selectedHtml = Win32IEHelper.GetSelectedHtml(item.Handle);

                            // Determine if we have a previously generated code snippet in the selected HTML.
                            var rx = new Regex(RXP_EXTRACT_CODE_SNIPPET, RXO_DEFAULTS);
                            if (string.IsNullOrEmpty(rx.Match(selectedHtml).Groups[RXG_CODE_SNIPPET].Value))
                            {
                                // The selected HTML is not recognized as a code snippet. Ask the user if they would
                                // like to process it anyway.
                                if (queryUserAboutSelectedText &&
                                    (DialogResult.Yes == MessageBox.Show(dialogOwner, Resources.ProcessSelectedTextAsCodeSnippet,
                                     frm.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2,
                                     CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft ? MessageBoxOptions.RightAlign | MessageBoxOptions.RtlReading : 0)))
                                {
                                    // Allow processing of selected HTML.
                                    processSelectedHtml = true;
                                }
                                // Once the user answsers this question once we should not ask them again while we swap between views.
                                queryUserAboutSelectedText = false;
                            }
                            else
                            {
                                // If the selected text is recognized as a code snippet simply process it.
                                processSelectedHtml = true;
                            }

                            // Determine if we should attempt to process the selected HTML as a code snippet.
                            if (processSelectedHtml)
                            {
                                // In order to correctly re-display the source code from the post we need to revert back
                                // some of the specific processing that is done when generating the HTML for it.
                                var codeSnippet = Regex.Replace(Regex.Replace(Regex.Replace(selectedHtml, RXP_EXTRACT_LNUM, String.Empty, RXO_DEFAULTS),
                                    RXP_EXTRACT_CRLF, Environment.NewLine, RXO_DEFAULTS), RXP_EXTRACT_HTML, string.Empty, RXO_DEFAULTS);
                                codeSnippet = codeSnippet.Replace("&lt;", "<");
                                codeSnippet = codeSnippet.Replace("&gt;", ">");
                                codeSnippet = codeSnippet.Replace("&amp;", "&");
                                codeSnippet = codeSnippet.Replace("&nbsp;", string.Empty);
                                // Extract the line numbers from the selected text without line numbers.
                                var lines = new List<string>(0);
                                var matches = new Regex(RXP_REPLACE_LNUM, RXO_DEFAULTS | RegexOptions.Multiline).Matches(codeSnippet);
                                foreach (Match match in matches)
                                {
                                    // Isolate the lines of code by removing the line number portion.
                                    lines.Add(match.Groups["line"].Value);
                                }
                                // If we did not find any matches it means the code snippet does not have line numbers.
                                // So, use the code snippet as is.
                                if (lines.Count > 0)
                                {
                                    // Otherwise, we need to join each line into a string again without the line numbers.
                                    codeSnippet = string.Join(string.Empty, lines.ToArray());
                                }
                                // Assign the code snippet to edit.
                                frm.CodeSnippetToEdit = codeSnippet;
                            }
                        }
                    }
                    // Show the respective dialog.
                    result = frm.ShowDialog(dialogOwner);
                    if (result == DialogResult.OK)
                    {
                        // Get the processed code snippet.
                        content = frm.CodeSnippet;
                    }
                    // Update the configuration.
                    config = frm.Config;
                    // Store the configuration.
                    config.Store();
                } while (result == DialogResult.Retry);
            }

            // Return the result of the operation.
            return result;
        }
        #endregion Overrides
    }
}